/*
  FUSE: Filesystem in Userspace
  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>

  This program can be distributed under the terms of the GNU GPL.
  See the file COPYING.

  gcc -Wall `pkg-config fuse --cflags --libs` fusexmp.c -o fusexmp
*/

#define FUSE_USE_VERSION 26

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef linux
/* For pread()/pwrite() */
#define _XOPEN_SOURCE 500
#endif

#include <fuse.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <sys/time.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include "config.h"
#include "bootsecto.h"
#include "directoryEntry.h"
#include "conexiones.h"
#include "utils.h"

#define FILEPATH "/home/utn_so/Desarrollo/Workspace/discoChico.disk"

#ifdef HAVE_SETXATTR
#include <sys/xattr.h>
#endif

char disk = 0;
TDD_entry *TDD;
int tamanioTDD = 0;
int tamanioCache = -1, maxSectoresCache, maxconexiones, lecturaEnProceso = 0;
bootSector bootSec;
pthread_mutex_t semConexiones;
sem_t semSockContador;


static int xmp_getattr(const char *path, struct stat *stbuf)
{
	int res;

	res = lstat(path, stbuf);
	if (res == -1)
		return -errno;

	return 0;
}

static int xmp_access(const char *path, int mask)
{
	int res;

	res = access(path, mask);
	if (res == -1)
		return -errno;

	return 0;
}

static int xmp_readlink(const char *path, char *buf, size_t size)
{
	int res;

	res = readlink(path, buf, size - 1);
	if (res == -1)
		return -errno;

	buf[res] = '\0';
	return 0;
}


static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
		       off_t offset, struct fuse_file_info *fi)
{
		int bool;
	    NodoEntry *lst = NULL,*aux;

    	 disk = open(FILEPATH, O_RDONLY);

	     bool = read_directory(disk,&lst,path,2);//boot.rootCluster=2

	     		if(lst!=NULL){

	     			while(lst != NULL){
	     				aux = lst;
	     			    filler(buf,lst->info.name, NULL, 0);
	     			    lst= aux->sig;
	     			    free(aux);
	     			}
	     		}

	     filler(buf, ".", NULL, 0);
	     filler(buf, "..", NULL, 0);

	return 0;
}


static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
{
	int res;

	/* On Linux this could just be 'mknod(path, mode, rdev)' but this
	   is more portable */
	if (S_ISREG(mode)) {
		res = open(path, O_CREAT | O_EXCL | O_WRONLY, mode);
		if (res >= 0)
			res = close(res);
	} else if (S_ISFIFO(mode))
		res = mkfifo(path, mode);
	else
		res = mknod(path, mode, rdev);
	if (res == -1)
		return -errno;

	return 0;
}

static int xmp_mkdir(const char *path, mode_t mode)
{
	int res;

	res = mkdir(path, mode);
	if (res == -1)
		return -errno;

	return 0;
}

static int xmp_unlink(const char *path)
{
	int res;

	res = unlink(path);
	if (res == -1)
		return -errno;

	return 0;
}

static int xmp_rmdir(const char *path)
{
	int res;

	res = rmdir(path);
	if (res == -1)
		return -errno;

	return 0;
}

static int xmp_symlink(const char *from, const char *to)
{
	int res;

	res = symlink(from, to);
	if (res == -1)
		return -errno;

	return 0;
}

static int xmp_rename(const char *from, const char *to)
{
	int res;

	res = rename(from, to);
	if (res == -1)
		return -errno;

	return 0;
}

static int xmp_link(const char *from, const char *to)
{
	int res;

	res = link(from, to);
	if (res == -1)
		return -errno;

	return 0;
}

static int xmp_chmod(const char *path, mode_t mode)
{
	int res;

	res = chmod(path, mode);
	if (res == -1)
		return -errno;

	return 0;
}

static int xmp_chown(const char *path, uid_t uid, gid_t gid)
{
	int res;

	res = lchown(path, uid, gid);
	if (res == -1)
		return -errno;

	return 0;
}

static int xmp_truncate(const char *path, off_t size)
{
	int res;

	res = truncate(path, size);
	if (res == -1)
		return -errno;

	return 0;
}

static int xmp_utimens(const char *path, const struct timespec ts[2])
{
	int res;
	struct timeval tv[2];

/*	tv[0].tv_sec = ts[0].tv_sec;
	tv[0].tv_usec = ts[0].tv_nsec / 1000;
	tv[1].tv_sec = ts[1].tv_sec;
	tv[1].tv_usec = ts[1].tv_nsec / 1000;

	res = utimes(path, tv);
	if (res == -1)
		return -errno;
*/
	return 0;
}

static int xmp_open(const char *path, struct fuse_file_info *fi)
{
	int res;

	res = open(path, fi->flags);
	if (res == -1)
		return -errno;

	close(res);
	return 0;
}

static int xmp_flush(const char *path, struct fuse_file_info *fi)
{
	char**newpath;
	unsigned int i = 1;
	int cont = 1;


	newpath = string_split2(path,'/');

	if((strcmp(path, "/") == 0)){
		newpath[1]=NULL;
	}

	while (newpath[cont]!=NULL){
		cont++;
	}

	while((strcmp(TDD[i].path, newpath[cont - 1]) != 0)){
		i++;
	}

	if(TDD[i].path == NULL){
		return 0;
	}

	if(TDD[i].cache->modificado == MODIFICADO){
			//bajarCache(path);
	}

	return 0;

}

static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
		    struct fuse_file_info *fi)
{
	int i, posicion, tamanio = 0;
	int bytesEnCluster, bytesEnCluster2, clustersAPedir, secPerBloque, sectoresEnCache = 0;
	unsigned int primerCluster, bloque, w, w2, primerSector, primerSector2, tamanioCluster, sector;
	sec_arch *sectores;
	div_t division;

	//TODO clusterDeUnArchivo(path, i);

	tamanioCluster = bootSec.sectorPerCluster*512;

	/*Calculo el tamaño de mi bloque*/
	bloque = (tamanioCluster / 2);
	secPerBloque = bootSec.sectorPerCluster/2;

	/* busco el cluster desde donde quiero leer */
	division = div(offset, tamanioCluster);

	/* si el offset cae en la mitad de un cluster, le sumo uno a la parte entera
	 * si el offset cae justo al final del cluster, le sumo uno a la parte entera para pedir el siguiente*/
	primerCluster = division.quot+1;

	/* posicion dentro del ultimo cluster desde donde quiero leer */
	w =  offset - (division.quot * tamanioCluster);
	bytesEnCluster = tamanioCluster- w;

	division = div((size - bytesEnCluster), tamanioCluster);
	if(division.rem != 0)
	{
		clustersAPedir = division.quot +1;
	}
	else
	{
		clustersAPedir = division.quot;
	}

	if(tamanioCache != 0)
	{
	posicion = posicionEnTDD(path);
	sectoresEnCache = TDD[posicion].cache->sectoresEnCache;
	}

	for(i=primerCluster; i<=(clustersAPedir+primerCluster);i++)
	{
		if((i==primerCluster) || (i==(clustersAPedir+primerCluster)))
		{
		if(i==primerCluster)
		{
			if(bloque> w)
			{
				primerSector = getSectorClusterN(primerCluster);
				/*getBloque(primerSectorLogicoALeer,
				 	 	 * 	numeroDeBloqueDentroDelCluster,
				 	 	 * 	 vectorEnElQueGuardaLosBuffer);
				 	 	 * 	 tiene que aumentar tamanio*/
				getBloque(primerSector, 1, secPerBloque,sectores, tamanio, posicion, sectoresEnCache);
				if ((bloque-w)< size)
				{
					getBloque(primerSector, 2, secPerBloque,sectores, tamanio, posicion, sectoresEnCache);
				}
			}
			else
			{
				getBloque(primerSector, 2,secPerBloque, sectores, tamanio, posicion, sectoresEnCache);
			}
		}

		if(i== (clustersAPedir+primerCluster))
		{
			bytesEnCluster2 = size-bytesEnCluster-(clustersAPedir*tamanioCluster);

			if(bytesEnCluster2 < 0)
			{
				primerSector2 = getSectorClusterN(clustersAPedir+primerCluster);
				w2 = tamanioCluster + bytesEnCluster2;
				if(bloque >= w2 )
				{
					getBloque(primerSector2, 1, secPerBloque,sectores, tamanio, posicion, sectoresEnCache);
				}
				else
				{
					getBloque(primerSector2, 1,secPerBloque, sectores, tamanio, posicion, sectoresEnCache);
					getBloque(primerSector2, 2, secPerBloque,sectores, tamanio, posicion, sectoresEnCache);
				}
			}
		}
		}else{

		//	sector = getSectorClusterN(clusterDeUnArchivo(path, i));
			getBloque(sector, 1,secPerBloque, sectores, tamanio, posicion, sectoresEnCache);
			getBloque(sector, 2,secPerBloque, sectores, tamanio, posicion, sectoresEnCache);
		}
	}

	armarBuf(sectores, tamanio, buf, w, w2, bloque, secPerBloque);

	return 0;
}

static int xmp_write(const char *path, const char *buf, size_t size,
		     off_t offset, struct fuse_file_info *fi)
{
	int 			clustersAPedir, bloquesAPedir, bytesGrabados, k;
	unsigned int 	cluster,  byteAEscribir;
	div_t 			division;
	int 			i, j = 0, posicion, tamanio, sectoresEnCache = 0, segundoBloque;
	int 			bytesEnCluster, freeClusters, bytesEnCluster2, ultimoCluster, secPerBloque;
	unsigned int 	primerCluster, bloque, sectorLibre,clusterLibre, w, w2, primerSector, primerSector2, tamanioCluster, sector;
	sec_arch 		sectores[secPerBloque];
	char 			*buffer, *buffer1;

	// Chequear en tabla global de archivos abiertos (TDD) si esta el path solicitado
	// si no esta, hay que gardarlo y crear la chache y guardar el puntero a la misma
	// si esta, hay que buscar en la cache del archivo los sectores solicitados y esccribirlos y marcar el bit de
	// modificado y sincronizar con el disco

	tamanioCluster = bootSec.sectorPerCluster * 512;

	/*Calculo el tamaño de mi bloque*/
	bloque = (tamanioCluster / 2);
	secPerBloque = bootSec.sectorPerCluster / 2;

	/* busco el cluster desde donde quiero escribir */
	division = div(offset, tamanioCluster);

	/* si el offset cae en la mitad de un cluster, le sumo uno a la parte entera
	 * si el offset cae justo al final del cluster, le sumo uno a la parte entera para pedir el siguiente*/
	primerCluster = division.quot + 1;

	/* posicion dentro del ultimo cluster desde donde quiero escribir */
	w =  offset - (division.quot * tamanioCluster);
	bytesEnCluster = tamanioCluster - w;

	/* TODO ver si tengo que pedir mas clusters */
	division = div((size - bytesEnCluster), tamanioCluster);
	if(division.rem != 0)
	{
		ultimoCluster = primerCluster + division.quot +1;
	}
	else
	{
		ultimoCluster = primerCluster + division.quot;
	}

	if(tamanioCache != 0)
	{
		posicion = posicionEnTDD(path);
		sectoresEnCache = TDD[posicion].cache->sectoresEnCache;
	}

	/* si es un nuevo archivo */
	if(offset == 0)
	{
		clustersAPedir = ultimoCluster - 1;
		bytesGrabados = 0;

		for(i = 0; i < clustersAPedir; i++)
		{
/*			freeClusters = getFreeClusters();
			clusterLibre = unqueue(freeClusters);
			sectorLibre = getSectorClusterN(clusterLibre);
			segundoBloque = 0;
*/
			division = div(size, bloque);
			if(division.rem != 0)
			{
				bloquesAPedir = division.quot +1;
			}
			else
			{
				bloquesAPedir = division.quot;
			}

			/* si voy a escribir mas de un bloque */
			if(bloquesAPedir >= 2)
			{
				for(k = 0; k < 2; k++)
				{
					/* pregunta del millon, que pasa si se termina el buf y el ciclo sigue? */
					for(j=0; j<secPerBloque; j++)
					{
						sectores[j].sector = sectorLibre + j + segundoBloque;
						memcpy(sectores[j].buffer, &buf[j*512 + bytesGrabados], 512);
					}
					bytesGrabados += bloque;
					segundoBloque = secPerBloque;
					/* segun como hagamos el setBloque si indicamos el numero de bloque,
					 * tenemos que llamar a la funcion dos veces, una por cada bloque */
					setBloque(primerSector, 1, secPerBloque, sectores, tamanio, posicion, sectoresEnCache);
				}
			}
			else /* escribo un solo bloque */
			{
				for(j=0; j<secPerBloque; j++)
				{
					sectores[0].sector = sectorLibre + j;
					memcpy(sectores[0].buffer, &buf[j*512 + bytesGrabados], 512);
				}
				bytesGrabados += bloque;
				setBloque(primerSector, 1, secPerBloque, sectores, tamanio, posicion, sectoresEnCache);
			}
		}
	}
	else /* si no es un nuevo archivo */
	{
		for(i = primerCluster; i <= ultimoCluster; i++)
		{
			if((i == primerCluster) || (i == ultimoCluster))
			{
				if(i == primerCluster)
				{
					if(bloque > w) /* si estoy en el primer bloque */
					{
//						cluster = clusterDeUnArchivo(path, primerCluster);

						primerSector = getSectorClusterN(cluster);

						sectores[0].sector = primerSector;

						getBloque(primerSector, 1, secPerBloque,sectores, tamanio, posicion, sectoresEnCache); // pido el 1er bloque

						/* Pregunto en que sector(no es el sector logico) empieza mi offset */
						division = div(w, 512);
						if(division.rem != 0)
						{
							sector = division.quot + 1;
						}
						else
						{
							sector = division.quot;
						}

						/* Me fijo de ese sector, en que byte tengo que empezar a escribir */
						byteAEscribir = 512 - w - (division.quot * 512);

						/* En el primer sector concateno lo que tenia con lo nuevo a escribir
						 * uso "sector - 1" porque es un vector */
						strncpy(buffer, buf, byteAEscribir);
						strncpy(buffer1, sectores[sector - 1].buffer, 512 - byteAEscribir);
						strcat(sectores[sector - 1].buffer, buffer1);
						strcat(sectores[sector - 1].buffer, buffer);

						/* TODO contemplar el caso que el buf sea menor a un bloque */

						/* Escribo en el resto de los sectores del bloque, lo que queda del buffer */
						for(i = sector; i < secPerBloque; i++)
						{
							sectores[i].sector = sectores[i - 1].sector + 1;
							memcpy(sectores[i].buffer, &buf[(512-byteAEscribir+1)*i], 512);
						}

						setBloque(primerSector, 1, secPerBloque, sectores, tamanio, posicion, sectoresEnCache);

						if ((bloque - w) < size)
						{
							getBloque(primerSector, 2, secPerBloque,sectores, tamanio, posicion, sectoresEnCache);
						}
					}
					else
					{
						//TODO preguntar si w = bloque no pido ningun bloque, y preguntar si w > bloque
					}
				}

				if(i== (ultimoCluster))
				{
					bytesEnCluster2 = size-bytesEnCluster-(ultimoCluster);

					if(bytesEnCluster2 < 0)
					{
						primerSector2 = getSectorClusterN(ultimoCluster);
						w2 = tamanioCluster + bytesEnCluster2;
						if(bloque >= w2 )
						{
							getBloque(primerSector2, 1, secPerBloque,sectores, tamanio, posicion, sectoresEnCache);
						}
						else
						{
							getBloque(primerSector2, 1,secPerBloque, sectores, tamanio, posicion, sectoresEnCache);
							getBloque(primerSector2, 2, secPerBloque,sectores, tamanio, posicion, sectoresEnCache);
						}
					}
				}
			}else{

//				sector = getSectorClusterN(clusterDeUnArchivo(path, i));
				getBloque(sector, 1,secPerBloque, sectores, tamanio, posicion, sectoresEnCache);
				getBloque(sector, 2,secPerBloque, sectores, tamanio, posicion, sectoresEnCache);
			}
		}
	}

	return 0;
}

static int xmp_statfs(const char *path, struct statvfs *stbuf)
{
	int res;

	res = statvfs(path, stbuf);
	if (res == -1)
		return -errno;

	return 0;
}

static int xmp_release(const char *path, struct fuse_file_info *fi)
{
	/* Just a stub.	 This method is optional and can safely be left
	   unimplemented */

	(void) path;
	(void) fi;
	return 0;
}

static int xmp_fsync(const char *path, int isdatasync,
		     struct fuse_file_info *fi)
{
	/* Just a stub.	 This method is optional and can safely be left
	   unimplemented */

	(void) path;
	(void) isdatasync;
	(void) fi;
	return 0;
}

#ifdef HAVE_SETXATTR
/* xattr operations are optional and can safely be left unimplemented */
static int xmp_setxattr(const char *path, const char *name, const char *value,
			size_t size, int flags)
{
	int res = lsetxattr(path, name, value, size, flags);
	if (res == -1)
		return -errno;
	return 0;
}

static int xmp_getxattr(const char *path, const char *name, char *value,
			size_t size)
{
	int res = lgetxattr(path, name, value, size);
	if (res == -1)
		return -errno;
	return res;
}

static int xmp_listxattr(const char *path, char *list, size_t size)
{
	int res = llistxattr(path, list, size);
	if (res == -1)
		return -errno;
	return res;
}

static int xmp_removexattr(const char *path, const char *name)
{
	int res = lremovexattr(path, name);
	if (res == -1)
		return -errno;
	return 0;
}
#endif /* HAVE_SETXATTR */

static struct fuse_operations xmp_oper = {
	.getattr	= xmp_getattr,
	.access		= xmp_access,
	.readlink	= xmp_readlink,
	.readdir	= xmp_readdir,
	.mknod		= xmp_mknod,
	.mkdir		= xmp_mkdir,
	.symlink	= xmp_symlink,
	.unlink		= xmp_unlink,
	.rmdir		= xmp_rmdir,
	.rename		= xmp_rename,
	.link		= xmp_link,
	.chmod		= xmp_chmod,
	.chown		= xmp_chown,
	.truncate	= xmp_truncate,
	.utimens	= xmp_utimens,
	.open		= xmp_open,
	.read		= xmp_read,
	.write		= xmp_write,
	.statfs		= xmp_statfs,
	.release	= xmp_release,
	.fsync		= xmp_fsync,
	.flush      = xmp_flush,

#ifdef HAVE_SETXATTR
	.setxattr	= xmp_setxattr,
	.getxattr	= xmp_getxattr,
	.listxattr	= xmp_listxattr,
	.removexattr	= xmp_removexattr,
#endif
};
//TODO avisar a los chicos que cuando se elimina, se cambia el nombre, se modifica un archivo hay que cambiarlo en la TDD


int main(int argc, char *argv[])
{
	int retval, retval1, maxconexiones;

	cargarConfig("PFS.config");

	getConfig("tamanioCache", &tamanioCache);
	getConfig("maxconexiones", &maxconexiones);

	retval = sem_init(&semSockContador, 0 ,maxconexiones);


	if(retval == -1) {
		retval1 = errno;
		printf("error en el sem_init: %d >Descripcion: %s\n.", retval1, strerror(errno));
	}

	atexit(clearConfig);

	connect_Raid_PPD();

 	getBootSectorSocket();

	printf("reservedSectors: %d\n.", bootSec.reservedSectors);
	printf("Cantidad de Tablas FAT: %d\n.", bootSec.fatCant);
	printf("Tamanio de la FAT: %d\n.", bootSec.fatSize);
	printf("Cluster donde empieza el Root Directory: %d\n.", bootSec.rootCluster);
	printf("Sectores por cluster: %d\n.", bootSec.sectorPerCluster);

	// pthread_create();
	return 0;

	umask(0);
	return fuse_main(argc, argv, &xmp_oper, NULL);
}
